﻿//_______________________________________________________________________________________________________________________
// Copy Right (c) 2010, 2011 SapiensIT Consulting GmbH, all rights reserved
//_______________________________________________________________________________________________________________________
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//_______________________________________________________________________________________________________________________
// PROJECT:     sapiens.at.SharePoint (SharePoint 2010/V4)
//_______________________________________________________________________________________________________________________
// VERSION:     2.0
//_______________________________________________________________________________________________________________________
// DESCRIPTION: 
//_______________________________________________________________________________________________________________________
// LICENSE:     Different licens Types apply to different file types:
//               - Source code files are governed by the MICROSOFT PUBLIC LICENSE (Ms-PL)
//               - Binary files are governed by MSDN CODE GALLERY BINARY LICENSE
//               - Documentation files are governed by CREATIVE COMMONS ATTRIBUTION 3.0 LICENSE
//
//               The licenses can be found at the projects source location.
//               If you do not agree, don't use it!
// 
//_______________________________________________________________________________________________________________________
// Start SharePoint Branding, visit http://www.cliggin.com! Visit us on http://www.sapiens.at! Tell your friends! 
//_______________________________________________________________________________________________________________________
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Net.Mail;
using System.Net.Mime;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web.Configuration;
using Microsoft.SharePoint;
using System.Globalization;
using Microsoft.SharePoint.Utilities;
using Sapiens.at.SharePoint.Mail;
using Sapiens.at.SharePoint.Mail.ICalendar;
using Microsoft.SharePoint.Administration;
using System.Linq;
using Sapiens.at.Core.Utils;

namespace Sapiens.at.SharePoint.Mail.ICalendar
{
    /// <summary>
    /// Represents an ICalendar Meeting Request
    /// </summary>
    public class EventMessage : MailMessageBase
    {
        #region EventHandler
        /// <summary>
        /// Response Handler
        /// </summary>
        /// <param name="Attendee">Attendee</param>
        /// <param name="Status">Response Status</param>
        public delegate void ResponseHandler(Attendee Attendee, string Status);
        /// <summary>
        /// Sent Handler
        /// </summary>
        public delegate void SentHandler();
        #endregion EventHandler
        /// <summary>
        /// Instantiates the meeting request
        /// </summary>
        /// <param name="Web"></param>
        public EventMessage(SPWeb Web)
            : base(Web)
        {
            Event = new Event();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="Web">SPWeb</param>
        /// <param name="iEvent">The event</param>
        public EventMessage(SPWeb Web, Event iEvent)
            : base(Web)
        {
            Event = iEvent;
        }
        #region Properties
        /// <summary>
        /// Represents the event in a calendar list
        /// </summary>
        public SPListItem ListItem { get; set; }
        /// <summary>
        /// Event of an ICalendar Meeting Request
        /// </summary>
        [CalendarKeyWordAttribute("BEGIN:VEVENT")]
        public Event Event { get; set; }
        /// <summary>
        /// Everything else
        /// </summary>
        [CalendarKeyWordAttribute("END:VEVENT")]
        public Event EndEvent { get; set; }
        private string _method = "REQUEST";
        /// <summary>
        /// defines the iCalendar object method associated with the calendar object
        /// Values: REQUEST/CANCEL
        /// Default: REQUEST
        /// </summary>
        [CalendarKeyWordAttribute("METHOD")]
        public string Method
        {
            get
            {
                return _method;
            }
            set
            {
                _method = value;
            }
        }
        [CalendarKeyWordAttribute("PRODID")]
        public string ProgID { get; set; }
        [CalendarKeyWordAttribute("VERSION")]
        public string Version { get; set; }
        #endregion
        #region EventHandler
        /// <summary>
        /// Is called after the <see cref="HandleResponse"/> method is processed
        /// </summary>
        public ResponseHandler OnReceived;
        /// <summary>
        /// is called after the meeting request is sent.
        /// </summary>
        public SentHandler OnSent;
        #endregion EventHandler
        #region Methods
        /// <summary>
        /// Splits up the <paramref name="ICalendarString"/> and sets all matching properties
        /// </summary>
        /// <param name="ICalendarString"></param>
        public void Init(string ICalendarString)
        {
            Utilities.SetProperties(this, ICalendarString);
        }
        /// <summary>
        /// Sends the ICalendar Meeting Request to all attendees
        /// </summary>
        public override void Send()
        {
            Send(null);
        }
        /// <summary>
        /// Sends the ICalendar Meeting Request to all attendees
        /// </summary>
        /// <param name="ReplyTo">ReplyTo Address</param>
        public void Send(string ReplyTo)
        {
            BuildMessage(ReplyTo);
            try
            {
                try
                {
                    base.Send();
                }
                catch (SmtpFailedRecipientException ex) { }
                catch (Exception ex) { throw ex; }
                if (OnSent != null) OnSent();
            }
            catch { }
        }
        /// <summary>
        /// Builds alternate Views
        /// 1. TextView
        /// 2. HtmlView
        /// 3. ICalendarView
        /// </summary>
        public void BuildMessage(string ReplyTo)
        {
            if ((Event.Attendees.Count > 0) && (!string.IsNullOrEmpty(Event.Subject)) && (!string.IsNullOrEmpty(Event.Organizer.EMail)))
            {
                System.Net.Mime.ContentType textType = new System.Net.Mime.ContentType("text/plain");
                System.Net.Mime.ContentType HTMLType = new System.Net.Mime.ContentType("text/html");
                System.Net.Mime.ContentType CalendarCT = new System.Net.Mime.ContentType("text/calendar");
                CalendarCT.Parameters.Add("method", Method);
                CalendarCT.Parameters.Add("charset", "UTF-8");
                CalendarCT.Parameters.Add("name", "meeting.ics");

                #region Body Text
                //plaintext body, if the email client is not able to read html
                string bodyText = "";
                bodyText += "Type:Single Meeting\r\n";
                bodyText += "Organizer: {0}\r\n";
                bodyText += "Start Time:{1}\r\n";
                bodyText += "End Time:{2}\r\n";
                bodyText += "Time Zone:{3}\r\n";
                bodyText += "Location: {4}\r\n";
                bodyText += "\r\n\r\n\r\n{5}";
                bodyText = string.Format(bodyText,
                                        Event.Organizer.EMail,
                                        Event.EventStart.ToShortDateString() + "" + Event.EventStart.ToShortTimeString(),
                                        Event.EventEnd.ToShortDateString() + "" + Event.EventEnd.ToShortTimeString(),
                                        System.TimeZone.CurrentTimeZone.StandardName,
                                        Event.Location,
                                        Event.Description == null ? "" : SPHttpUtility.ConvertSimpleHtmlToText(Event.Description, Event.Description.Length)
                                        );
                AlternateView textView = AlternateView.CreateAlternateViewFromString(bodyText, textType);
                Message.AlternateViews.Add(textView);
                #endregion

                #region BodyHTML
                //htmlbody
                string bodyHTML = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">\r\n<HTML>\r\n<HEAD>\r\n" +
                    "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=utf-8\">\r\n" +
                    "<META NAME=\"Generator\" CONTENT=\"MS Exchange Server version 6.5.7652.24\">\r\n" +
                    "<TITLE>{0}</TITLE>\r\n</HEAD>\r\n<BODY>\r\n<!-- Converted from text/plain format -->\r\n" +
                    "<P><FONT>" +
                    "Type:Single Meeting<BR>" +
                    "\r\nOrganizer:{1}<BR>" +
                    "\r\nStart Time:{2}<BR>" +
                    "\r\nEnd Time:{3}<BR>" +
                    "\r\nTime Zone:{4}<BR>" +
                    "\r\nLocation:{5}<BR>" +
                    "\r\n<BR>\r\n*~*~*~*~*~*~*~*~*~*<BR>\r\n<BR>\r\n{6}<BR>" +
                    "\r\n</FONT>\r\n</P>\r\n\r\n" +
                    "</BODY>\r\n</HTML>";
                bodyHTML = string.Format(bodyHTML,
                    Event.Subject,
                    Event.Organizer.CN,
                    Event.EventStart.ToLongDateString() + " " + Event.EventStart.ToLongTimeString(),
                    Event.EventEnd.ToLongDateString() + " " + Event.EventEnd.ToLongTimeString(),
                    System.TimeZone.CurrentTimeZone.StandardName,
                    Event.Location,
                    Event.Description
                    );
                AlternateView HTMLView = AlternateView.CreateAlternateViewFromString(bodyHTML, HTMLType);
                Message.AlternateViews.Add(HTMLView);
                #endregion

                #region Calendar
                #region VCalendar
                string calDateFormat = "yyyyMMddTHHmmssZ";
                //CalendarView
                string bodyCalendar = "BEGIN:VCALENDAR\r\n";
                bodyCalendar += "METHOD:" + Method + "\r\n";
                bodyCalendar += "PRODID:Microsoft CDO for Microsoft Exchange\r\n";
                bodyCalendar += "VERSION:2.0\r\n";
                //gets the UTC offset
                string UTCOffset = Utilities.GetCurrentTimeZone().BaseUtcOffset.Hours.ToString("D2");
                if (!UTCOffset.StartsWith("-")) UTCOffset = "+" + UTCOffset;
                string Minutes = Utilities.GetCurrentTimeZone().BaseUtcOffset.Minutes.ToString("D2");
                UTCOffset = UTCOffset + Minutes;
                #region VTimeZone
                bodyCalendar += "BEGIN:VTIMEZONE\r\n";
                bodyCalendar += "TZID:" + Utilities.GetCurrentTimeZone().Id + "\r\n";
                #region Standard
                bodyCalendar += "BEGIN:STANDARD\r\n";
                bodyCalendar += "DTSTART:" + Event.EventStart.ToUniversalTime().ToString(calDateFormat) + "\r\n";
                bodyCalendar += "TZOFFSETFROM:" + UTCOffset + "\r\n";
                bodyCalendar += "TZOFFSETTO:" + UTCOffset + "\r\n";
                bodyCalendar += "END:STANDARD\r\n";
                #endregion
                bodyCalendar += "END:VTIMEZONE\r\n";
                #endregion
                #region VEvent
                bodyCalendar += "BEGIN:VEVENT\r\n";
                bodyCalendar += "DTSTAMP:" + Event.DTStamp.ToUniversalTime().ToString(calDateFormat) + "\r\n";
                bodyCalendar += "DTSTART:" + Event.EventStart.ToUniversalTime().ToString(calDateFormat) + "\r\n";
                bodyCalendar += "SUMMARY:" + Event.Subject + "\r\n";
                bodyCalendar += "UID:" + Event.UID + "\r\n";
                for (int i = 0; i < Event.Attendees.Count; i++)
                {
                    if (!string.IsNullOrEmpty(Event.Attendees[i].EMail))
                    {
                        bodyCalendar += "ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CN=\"{4}\";PARTSTAT=NEEDS-ACTION:MAILTO:" + Event.Attendees[i].EMail + "\r\n";
                        bodyCalendar += "CN=\"" + Event.Attendees[i].CN + "\":MAILTO:" + Event.Attendees[i].EMail + "\r\n";
                    }
                }
                bodyCalendar += "ORGANIZER;CN=\"" + Event.Organizer.CN + "\":mailto:" + Event.Organizer.EMail + "\r\n";
                bodyCalendar += "LOCATION:" + Event.Location + "\r\n";
                bodyCalendar += "DTEND:" + Event.EventEnd.ToUniversalTime().ToString(calDateFormat) + "\r\n";
                bodyCalendar += "DESCRIPTION:" + (Event.Description == null ? "" : Microsoft.SharePoint.Utilities.SPHttpUtility.ConvertSimpleHtmlToText(Event.Description, Event.Description.Length)) + "\\N\r\n";
                bodyCalendar += "SEQUENCE:" + Event.Sequence + "\r\n";
                bodyCalendar += "PRIORITY:" + Event.Priority + "\r\n";
                bodyCalendar += "CLASS:" + Event.Class + "\r\n";
                bodyCalendar += "CREATED:" + Event.DTStamp.ToUniversalTime().ToString(calDateFormat) + "\r\n";
                bodyCalendar += "LAST-MODIFIED:" + Event.DTStamp.ToUniversalTime().ToString(calDateFormat) + "\r\n";
                bodyCalendar += "STATUS:" + Event.Status + "\r\n";
                bodyCalendar += "TRANSP:" + Event.Transp + "\r\n";
                bodyCalendar += "X-MICROSOFT-CDO-BUSYSTATUS:BUSY\r\n";
                bodyCalendar += "X-MICROSOFT-CDO-INSTTYPE:0\r\n";
                bodyCalendar += "X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY\r\n";
                bodyCalendar += "X-MICROSOFT-CDO-ALLDAYEVENT:FALSE\r\n";
                bodyCalendar += "X-MICROSOFT-CDO-IMPORTANCE:1\r\n";
                bodyCalendar += "X-MICROSOFT-CDO-OWNERAPPTID:-1\r\n";
                bodyCalendar += "X-MICROSOFT-CDO-ATTENDEE-CRITICAL-CHANGE:" + Event.DTStamp.ToUniversalTime().ToString(calDateFormat) + "\r\n";
                bodyCalendar += "X-MICROSOFT-CDO-OWNER-CRITICAL-CHANGE:" + Event.DTStamp.ToUniversalTime().ToString(calDateFormat) + "\r\n";
                #region VAlarm
                bodyCalendar += "BEGIN:VALARM\r\n";
                bodyCalendar += "ACTION:DISPLAY\r\n";
                bodyCalendar += "DESCRIPTION:REMINDER\r\n";
                //trigger alarm
                string AlarmHours = Event.AlarmBeforeEventStarts.Hours.ToString("D2");
                string AlarmMinutes = Event.AlarmBeforeEventStarts.Minutes.ToString("D2");
                string AlarmSeconds = Event.AlarmBeforeEventStarts.Seconds.ToString("D2");
                bodyCalendar += "TRIGGER;RELATED=START:-PT" + AlarmHours + "H" + AlarmMinutes + "M" + AlarmSeconds + "S\r\n";
                bodyCalendar += "END:VALARM\r\n";
                #endregion
                bodyCalendar += "END:VEVENT\r\n";
                #endregion
                bodyCalendar += "END:VCALENDAR\r\n";
                #endregion
                AlternateView calendarView = AlternateView.CreateAlternateViewFromString(bodyCalendar, CalendarCT);
                calendarView.TransferEncoding = TransferEncoding.SevenBit;
                Message.AlternateViews.Add(calendarView);
                #endregion
                foreach (Attendee att in Event.Attendees)
                {
                    if (!string.IsNullOrEmpty(att.EMail)) Message.To.Add(att.EMail);
                }
                Message.From = new MailAddress(Event.Organizer.EMail);
                Message.Subject = Event.Subject;
                Message.ReplyTo = (string.IsNullOrEmpty(ReplyTo)) ? new MailAddress(Event.Organizer.EMail) : new MailAddress(ReplyTo);
                Message.Sender = (string.IsNullOrEmpty(ReplyTo)) ? new MailAddress(Event.Organizer.EMail) : new MailAddress(ReplyTo);
            }
        }
        /// <summary>
        /// Handles the Response
        /// Use OnReceived to handle the different response state
        /// </summary>
        /// <param name="List"></param>
        /// <param name="From"></param>
        /// <param name="Body"></param>
        public void HandleResponse(SPList List, SPUser From, string Body)
        {
            if (Body.Contains("Content-Type: text/calendar"))
            {
                string ICalType = Body.Substring(Body.ToLower().IndexOf("content-type: text/calendar"));
                if (ICalType.ToLower().Contains("content-transfer-encoding: base64"))
                {
                    //decode from base 64
                    string messageID = Body.Substring(Body.IndexOf("Content-Type: text/calendar"));
                    messageID = messageID.Substring(messageID.IndexOf("Content-Transfer-Encoding:"));
                    messageID = messageID.Remove(0, messageID.IndexOf("\r\n\r\n") + 4);
                    messageID = messageID.Remove(messageID.IndexOf("\r\n\r\n"), messageID.LastIndexOf("_--") + 3 - messageID.IndexOf("\r\n\r\n"));
                    messageID.Trim();
                    byte[] toDecodeByte = Convert.FromBase64String(messageID);
                    System.Text.UTF8Encoding encoder = new System.Text.UTF8Encoding();
                    System.Text.Decoder utf8Decode = encoder.GetDecoder();
                    int charCount = utf8Decode.GetCharCount(toDecodeByte, 0, toDecodeByte.Length);
                    char[] decodedChar = new char[charCount];
                    utf8Decode.GetChars(toDecodeByte, 0, toDecodeByte.Length, decodedChar, 0);
                    string result = new String(decodedChar);
                    ICalType = Body + result;
                }
                else
                {
                    ICalType = Body;
                }

                string VCalendarBegin = "BEGIN:VCALENDAR";
                string VCalendarEnd = "END:VCALENDAR";
                string VCalendar = string.Empty;

                if ((ICalType.IndexOf(VCalendarBegin) > 0) && (ICalType.IndexOf(VCalendarEnd) > 0))
                    VCalendar = ICalType.Split(new string[] { VCalendarBegin }, StringSplitOptions.None)[1].Split(new string[] { VCalendarEnd }, StringSplitOptions.None)[0];
                //Set the Properties of the ICalendar, IEvent, IAttendees and IOrganizer
                Init(VCalendar);
                try
                {
                    //get the event in the calendar list
                    string uid = Event.UID.Length > 36 ? Event.UID.Substring(0, 36) : Event.UID;
                    Guid gu = new Guid(uid);
                    ListItem = List.GetItemByUniqueId(gu);
                }
                catch
                {
                    //meeting was directly sent to the trainings list
                }
                if ((ListItem != null) && (OnReceived != null))
                {
                    //call the event handler
                    OnReceived(Event.Attendees[0], Event.Attendees[0].ParticipationStatus.ToUpper());
                }
            }
        }
        /// <summary>
        /// Handles the Response
        /// Use OnReceived to handle the different response state
        /// </summary>
        /// <param name="List"></param>
        /// <param name="Body"></param>
        public void HandleResponse(SPList List, string Body)
        {
            HandleResponse(List, null, Body);
        }
        #endregion
        #region Static Factories
        /// <summary>
        /// Gets the ICalendar Object to the given <paramref name="iEvent"/>
        /// </summary>
        /// <param name="Web">SPWeb</param>
        /// <param name="icEvent">ICEvent</param>
        /// <param name="user">Attendee</param>
        /// <returns>returns the ICalendar Object</returns>
        public static EventMessage Create(SPWeb Web, Entities.Event eventEntity, SPUser user)
        {
            //Attendee = User
            return Create(Web, eventEntity, user.Email, user.Name);
        }

        /// <summary>
        /// Gets the ICalendar Object to the given <paramref name="icEvent"/>
        /// </summary>
        /// <param name="Web">SPWeb</param>
        /// <param name="icEvent">ICEvent</param>
        /// <param name="email">E-Mail Address</param>
        /// <param name="name">Name</param>
        /// <returns>returns the ICalendar Object</returns>
        public static EventMessage Create(SPWeb Web, Entities.Event eventEntity, string email, string name)
        {
            //Attendee = User
            return Create(Web, eventEntity, new List<Attendee> { new Attendee(email, name) });
        }
        /// <summary>
        /// Gets the ICalendar Object to the given <paramref name="icEvent"/>
        /// </summary>
        /// <param name="Web">SPWeb</param>
        /// <param name="icEvent">ICEvent</param>
        /// <param name="users">Attendees</param>
        /// <param name="externalAttendees">external Attendees</param>
        /// <returns>returns the ICalendar Object</returns>
        public static EventMessage Create(SPWeb Web, Entities.Event eventEntity, IEnumerable<SPUser> users, string[] externalAttendees)
        {
            //Attendee = User
            List<Attendee> Attendees = users.Where(u => !string.IsNullOrEmpty(u.Email)).Select(u => new Attendee(u.Email, u.Name)).ToList();

            foreach (string email in externalAttendees) if (email.IsEMailAddress()) Attendees.Add(new Attendee(email, email));

            return Create(Web, eventEntity, Attendees);
        }
        /// <summary>
        /// Gets the ICalendar Object to the given <paramref name="iEvent"/>
        /// </summary>
        /// <param name="Web">SPWeb</param>
        /// <param name="iEvent">ICEvent</param>
        /// <param name="attendees">Attendees</param>
        /// <returns>returns the ICalendar Object</returns>
        public static EventMessage Create(SPWeb Web, Entities.Event eventEntity, IEnumerable<Attendee> Attendees)
        {
            //Organiser = Trainings List
            string OrganiserEMail = string.Empty;
            if (!string.IsNullOrEmpty(eventEntity.List.EmailAlias))
            {
                string mailDomain = ((SPIncomingEmailService)Web.Site.WebApplication.Farm.GetChild<SPIncomingEmailService>()).ServerDisplayAddress;
                OrganiserEMail = string.Format("{0}@{1}", eventEntity.List.EmailAlias, mailDomain);
            }
            return new EventMessage(Web,
                new Event(
                    eventEntity.EventDate,
                    eventEntity.EndDate,
                    eventEntity.Unique_ID.ToString(),
                    new Organizer(OrganiserEMail, OrganiserEMail),
                    Attendees.ToList(),
                    eventEntity.Description,
                    eventEntity.Title,
                    eventEntity.Location)
                );
        }
        #endregion
    }
}

